using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace WaterExample
{   
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Game
    {
        SpriteBatch _spriteBatch;

        private RenderTarget2D _renderTargetCubeMapFace;
        private RenderTarget2D _renderTargetCubeMapFaceTemp;
        private TextureCube _textureCube;
        private Color[] _faceData;
        private Matrix _defaultView;
        private Matrix _defaultProjection;
        private Matrix _cubeMapProjection;
        private readonly Matrix[] _cubeMapMatrices = new Matrix[6];  

        private Terrain _terrain;
        private readonly WaterC _waterC;

        private SpriteFont _font;
        public static Vector3 LightDirection = new Vector3(1, -1, 0);
        public static Vector3 LightColor = Color.LightYellow.ToVector3()*0.75f;

        public Game1()
        {
            new GraphicsDeviceManager(this)
                {PreferredBackBufferWidth = 1280, PreferredBackBufferHeight = 860};
            Content.RootDirectory = "Content";

            Camera camera = new TargetCamera(new Vector3(100, 200, 0), Vector3.Zero, this);
            Components.Add(camera);

            var skyBox = new SkyBox(this, "Models/skybox", "Shaders/skybox", "Textures/cubeMap");
            Components.Add(skyBox);

            var waterOptions = new WaterOptions
                {
                    CellSpacing = 8f,
                    WaveMapScale = 2,
                    Height = 256,
                    Width = 256,
                    WaveMapVelocity0 = new Vector2(0.01f, 0.01f),
                    WaveMapVelocity1 = new Vector2(0.01f, 0.02f),
                    WaterColor = new Vector4(0, 0.15f, 0.115f, 1),
                    WaterPosition = new Vector3(0, 0, 0)
                };

            _waterC = new WaterC(this) {Options = waterOptions, WaveHeight = 0};
            Components.Add(_waterC);
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            _spriteBatch = new SpriteBatch(GraphicsDevice);
            _font = Content.Load<SpriteFont>("Text");

            _terrain = new Terrain(4, 500, 12, this);

            _renderTargetCubeMapFace = new RenderTarget2D(GraphicsDevice, 512, 512,
               false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
            _renderTargetCubeMapFaceTemp = new RenderTarget2D(GraphicsDevice, 512, 512,
               false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);

            _textureCube = new TextureCube(GraphicsDevice, 512, false, SurfaceFormat.Color);
            _faceData = new Color[512 * 512];

            _defaultView = Camera.View;
            _defaultProjection = Camera.Projection;

            LoadCubeMatrices();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();
            
            LoadCubeMatrices();  

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.BlendState = BlendState.Opaque;

            CreateDynamicCubeMap(gameTime);

            DrawObjects(gameTime); 

            DrawInfoText();
        }

        private void CreateDynamicCubeMap(GameTime gameTime)
        {
            _defaultView = Camera.View;
            Camera.Projection = _cubeMapProjection;

            for (int i = 0; i < _cubeMapMatrices.Length; i++)
            {
                Camera.View = _cubeMapMatrices[i];

                RenderCubeMapFace(gameTime);

                FlipRenderTargetHorizontal();

                _renderTargetCubeMapFace.GetData(_faceData);
                _textureCube.SetData((CubeMapFace)i, _faceData);
            } 
        }

        private void RenderCubeMapFace(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.White);
            GraphicsDevice.SetRenderTargets(_renderTargetCubeMapFaceTemp);

            _terrain.Draw(Camera.View, Camera.Projection);
            base.Draw(gameTime);

            GraphicsDevice.SetRenderTargets(null); 
        }

        private void FlipRenderTargetHorizontal()
        {
            GraphicsDevice.SetRenderTargets(_renderTargetCubeMapFace);
            _spriteBatch.Begin();
            _spriteBatch.Draw(_renderTargetCubeMapFaceTemp, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height),
                                new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White, 0,
                                Vector2.Zero, SpriteEffects.FlipHorizontally, 1);
            _spriteBatch.End();
            GraphicsDevice.SetRenderTargets(null); 
        }

        private void DrawObjects(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.White);
            Camera.View = _defaultView;
            Camera.Projection = _defaultProjection;

            _waterC.CubeMap = _textureCube;
            _waterC.SetEffectParameter();

            _terrain.Draw(Camera.View, Camera.Projection);
            _waterC.Draw();

            base.Draw(gameTime);
        }

        private void DrawInfoText()
        {
            _spriteBatch.Begin();

            _spriteBatch.DrawString(_font, "WaveHeight(+/-): " + _waterC.WaveHeight.ToString("F"), new Vector2(0, 0), Color.White);
            _spriteBatch.DrawString(_font, "WaveFrequenzy(O/L): " + _waterC.Options.WaveMapScale.ToString("F"), new Vector2(0, 20), Color.White);

            _spriteBatch.End();
        }        

        private void LoadCubeMatrices()
        {
            _cubeMapProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver2, 1, 0.1f, 10000f);

            Vector3 pos = Camera.Position;
            _cubeMapMatrices[0] = Matrix.CreateLookAt(pos,pos + Vector3.UnitX, Vector3.Up);
            _cubeMapMatrices[1] = Matrix.CreateLookAt(pos,pos - Vector3.UnitX, Vector3.Up);
            _cubeMapMatrices[2] = Matrix.CreateLookAt(pos,pos + Vector3.UnitY, -Vector3.UnitZ);//top
            _cubeMapMatrices[3] = Matrix.CreateLookAt(pos,pos - Vector3.UnitY, Vector3.UnitZ);//bottom  
            _cubeMapMatrices[4] = Matrix.CreateLookAt(pos,pos + Vector3.UnitZ, Vector3.Up);
            _cubeMapMatrices[5] = Matrix.CreateLookAt(pos,pos - Vector3.UnitZ, Vector3.Up);
        }
    }
}
